package tail.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

/** 
 * Standard way of reading/writing properties.
 * @author Michael Sena
 * @version 1.1
 * @date 01/07/2010
 */
public abstract class Properties {
    /** Singleton implementation of a properties class. */
    private static java.util.Properties staticProperties = null;
    
    /** File name of the settings properties file. */
    private static final String PROPERTIES_FILE = "settings.properties";
    
    /** Standard separator used for arrays. */
    public static final String DELIM = ",";

    /** Default private constructor. */
    private Properties() { }
    /**
     * Singleton access to the properties class.
     * Ensures the file has been created and is readable.
     * @return Properties object
     */
    private static java.util.Properties instance() {
        if (staticProperties == null) {
            staticProperties = new java.util.Properties();
            File propsFile = new File(PROPERTIES_FILE);
            try {
                if (!propsFile.exists()) {
                    propsFile.createNewFile();
                }
                staticProperties.load(new FileInputStream(PROPERTIES_FILE));
            } catch (IOException ioe) {
                Logger.log(Properties.class.getName(),
                        "Error loading properties file."
                        + "Will move old properties file and re-create.",
                        ioe);
                if (propsFile.exists()) {
                    if (!propsFile.renameTo(
                            new File(PROPERTIES_FILE + ".old"))) {
                        Logger.log(Properties.class.getName(),
                                "Unable to move old properties file.");
                    }
                }
            }
        }
        return staticProperties;
    }

    /** 
     * Gets a property.
     * @param propName Name of the property
     * @return String value of the property
     */
    public static String get(final Property propName) {
        if(instance().getProperty(propName.toString()) == null) {
            set(propName, propName.getDefaultValue());
        }
        return instance().getProperty(propName.toString());
    }

    /**
     * Gets a boolean property.
     * @param propName Name of the property.
     */
    public static boolean getBool(final Property propName) {
        return Boolean.parseBoolean(get(propName));
    }

    /**
     * Sets a property.
     * @param propName Name of the property
     * @param value Value to store
     */
    public static void set(final Property propName, final String value) {
        instance().setProperty(propName.toString(), value);
    }

    /**
     * Stores an array-type property.
     * @param propName Name of the property
     * @param values Array values
     */
    public static void setArray(final Property propName,
            final ArrayList<String> values) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < values.size(); i++) {
            if (i != 0) {
                result.append(DELIM);
            }
            result.append(values.get(i));
        }
        instance().put(propName.toString(), result);
    }

    /**
     * Stores an array-type property, optionally sorting the list first.
     * This uses setArray to actually store the property.
     * @param propName Name of the property
     * @param values Array values
     * @param sort Whether to sort the list or not.
     */
    public static void setArray(final Property propName,
            final ArrayList<String> values, final boolean sort) {
        if (sort) {
            Collections.sort(values);
        }
        setArray(propName, values);
    }

    /**
     * Saves the properties file.
     */
    public static void save() {
        try {
            instance().store(new FileOutputStream(PROPERTIES_FILE),
                    "--no comment--");
        } catch (IOException ex) {
            Logger.log(Properties.class.getName(),
                    "Error saving properties file", ex);
        }
    }

    /**
     * Gets an array-type property.
     * @param propName Name of the property
     * @return A constructed array
     */
    public static ArrayList<String> getArray(final Property propName) {
        return getArray(propName, false);
    }

    /**
     * Gets an array-type property (ensures a blank array is returned).
     * @param propName Name of the property
     * @param looped Whether to loop or not
     * @return Array representation of the property
     */
    private static ArrayList<String> getArray(final Property propName,
            final boolean looped) {
        ArrayList<String> values = new ArrayList<>();
        String strValues = get(propName);        
        if (strValues != null && !strValues.isEmpty()) {
            try {
                values.addAll(Arrays.asList(strValues.split(DELIM)));
            } catch (ClassCastException cce) {
                Logger.log(Properties.class.getName(),
                        "Unable to load " + propName.toString()
                        + " values.  Default values will now be reloaded.",
                        cce);
                values = null;
            }
        }

        if (values == null || values.isEmpty()) {
            if (!looped) {
                set(propName, propName.getDefaultValue());
                getArray(propName, true);
            } else {
                Logger.log(Properties.class.getName(),
                    "Unable to set default array values for "
                    + propName.toString());
            }
        }

        return values;
    }
    
    /**
     * Whether or not the property has updated.
     * Uses hash values to make the comparison.
     * @param propName Name of the property
     * @param hashCode Hash of the perceived value
     * @return Whether the property has updated since.
     */
    public static boolean hasUpdated(final Property propName,
        final int hashCode) {
        return (Properties.get(propName).hashCode() != hashCode);
    }
}
